/**
 * (created at 2011-2-17)
 */
package qiushuo.mysqlparser.recognizer.parser;

import static qiushuo.mysqlparser.recognizer.SQLTokenType.IDENTIFIER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_FOR;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_FROM;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_IN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_LIKE;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_LIMIT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_SET;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_WHERE;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.PUNC_COMMA;

import java.sql.SQLSyntaxErrorException;
import java.util.LinkedList;
import java.util.List;

import qiushuo.mysqlparser.ast.expression.Expression;
import qiushuo.mysqlparser.ast.statement.DALSetStatement;
import qiushuo.mysqlparser.ast.statement.DALSetStatement.KeyType;
import qiushuo.mysqlparser.ast.statement.DALShowStatement;
import qiushuo.mysqlparser.recognizer.SQLToken;
import qiushuo.mysqlparser.recognizer.TokenReader;
import qiushuo.mysqlparser.util.Pair;

/**
 * @author <a href="mailto:QiuShuo1985@gmail.com">QIU Shuo</a>
 */
public class DALParser extends StatementParser {
    private ExpressionParser exprParser;

    public DALParser(TokenReader input) {
        super(input);
        this.exprParser = new ExpressionParser(input);
    }

    private List<DALShowStatement.Type> getTypeList() {
        List<DALShowStatement.Type> list = new LinkedList<DALShowStatement.Type>();
        try {
            while (true) {
                SQLToken t = input.next();
                if (t == null) return list;
                switch (t.getType()) {
                case KW_ALL:
                    list.add(DALShowStatement.Type.ALL);
                    break;
                case IDENTIFIER:
                    String text = t.getText().toUpperCase();
                    if ("BLOCK".equals(text) && input.isNext(IDENTIFIER, 0, true, "IO")) {
                        input.skipForce(1);
                        list.add(DALShowStatement.Type.BLOCK_IO);
                    } else if ("CONTEXT".equals(text) && input.isNext(IDENTIFIER, 0, true, "SWITCHES")) {
                        input.skipForce(1);
                        list.add(DALShowStatement.Type.CONTEXT_SWITCHES);
                    } else if ("CPU".equals(text)) {
                        list.add(DALShowStatement.Type.CPU);
                    } else if ("IPC".equals(text)) {
                        list.add(DALShowStatement.Type.CPU);
                    } else if ("MEMORY".equals(text)) {
                        list.add(DALShowStatement.Type.MEMORY);
                    } else if ("PAGE".equals(text) && input.isNext(IDENTIFIER, 0, true, "FAULTS")) {
                        input.skipForce(1);
                        list.add(DALShowStatement.Type.PAGE_FAULTS);
                    } else if ("SOURCE".equals(text)) {
                        list.add(DALShowStatement.Type.SOURCE);
                    } else if ("SWAPS".equals(text)) {
                        list.add(DALShowStatement.Type.SWAPS);
                    }
                default:
                    input.backForce();
                    return list;
                }
                if (input.isNext(PUNC_COMMA, 0)) {
                    input.skipForce(1);
                    continue;
                }
                return list;
            }
        } finally {
            if (list.isEmpty()) {
                return null;
            }
        }
    }

    /**
     * first <code>SHOW</code> has been consumed
     */
    public DALShowStatement showStmt() throws SQLSyntaxErrorException {
        SQLToken token = input.next();
        if (token == null) {
            throw err("EOF for SHOW");
        }
        DALShowStatement.Show show = null;
        boolean property1 = false;
        String name1 = null;
        String name2 = null;
        Expression info1 = null;
        Expression info2 = null;
        Expression info3 = null;
        Expression info4 = null;
        Expression like = null;
        Expression where = null;
        List<DALShowStatement.Type> typeList = null;
        switch (token.getType()) {
        case IDENTIFIER:
            String text = token.getText().toUpperCase();
            if ("AUTHORS".equals(text)) {
                return new DALShowStatement(
                        DALShowStatement.Show.AUTHORS,
                        false,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null);
            } else if ("MASTER".equals(text)) {
                if (input.isNext(IDENTIFIER, 0, true, "LOGS")) {
                    input.skipForce(1);
                    return new DALShowStatement(
                            DALShowStatement.Show.LOGS,
                            true,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null);
                } else if (input.isNext(IDENTIFIER, 0, true, "STATUS")) {
                    input.skipForce(1);
                    return new DALShowStatement(
                            DALShowStatement.Show.MASTER,
                            property1,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null);
                }
            } else if ("BINLOG".equals(text)) {
                match(IDENTIFIER, true, "EVENTS");
                if (input.isNext(KW_IN, 0)) {
                    info1 = exprParser.expression();
                }
                if (input.isNext(KW_FROM, 0)) {
                    info2 = exprParser.expression();
                }
                if (input.isNext(KW_LIMIT, 0)) {
                    info4 = exprParser.expression();
                    if (input.isNext(PUNC_COMMA, 0)) {
                        info3 = info4;
                        input.skipForce(1);
                        info4 = exprParser.expression();
                    }
                }
                return new DALShowStatement(
                        DALShowStatement.Show.BINLOG,
                        false,
                        null,
                        null,
                        info1,
                        info2,
                        info3,
                        info4,
                        null,
                        null,
                        null);
            } else if ("COLLATION".equals(text)) {
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        DALShowStatement.Show.COLLATION,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("COLUMNS".equals(text)) {
                match(KW_FROM);
                name1 = match(IDENTIFIER).getText();
                if (input.isNext(KW_FROM, 0)) {
                    input.skipForce(1);
                    name2 = match(IDENTIFIER).getText();
                }
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        DALShowStatement.Show.COLUMNS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);

            } else if ("FULL".equals(text)) {
                property1 = true;
                token = match(IDENTIFIER);
                if ("PROCESSLIST".equalsIgnoreCase(token.getText())) {
                    return new DALShowStatement(
                            DALShowStatement.Show.PROCESSLIST,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                } else if ("COLUMNS".equalsIgnoreCase(token.getText())) {
                    match(KW_FROM);
                    name1 = match(IDENTIFIER).getText();
                    if (input.isNext(KW_FROM, 0)) {
                        input.skipForce(1);
                        name2 = match(IDENTIFIER).getText();
                    }
                    if (input.isNext(KW_LIKE, 0)) {
                        input.skipForce(1);
                        like = exprParser.expression();
                    } else {
                        match(KW_WHERE);
                        where = exprParser.expression();
                    }
                    return new DALShowStatement(
                            DALShowStatement.Show.COLUMNS,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                } else if ("TABLES".equalsIgnoreCase(token.getText())) {
                    if (input.isNext(KW_FROM, 0)) {
                        input.skipForce(1);
                        name1 = match(IDENTIFIER).getText();
                    }
                    if (input.isNext(KW_LIKE, 0)) {
                        input.skipForce(1);
                        like = exprParser.expression();
                    } else {
                        match(KW_WHERE);
                        where = exprParser.expression();
                    }
                    return new DALShowStatement(
                            DALShowStatement.Show.TABLES,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                }
            } else if ("CONTRIBUTORS".equals(text)) {
                return new DALShowStatement(
                        DALShowStatement.Show.CONTRIBUTORS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("ENGINE".equals(text)) {
                name1 = match(IDENTIFIER).getText();
                if (input.isNext(IDENTIFIER, 0, true, "MUTEX")) {
                    property1 = true;
                } else {
                    match(IDENTIFIER, true, "STATUS");
                    property1 = false;
                }
                return new DALShowStatement(
                        DALShowStatement.Show.ENGINE,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("STORAGE".equals(text)) {
                property1 = true;
                match(IDENTIFIER, true, "ENGINES");
                return new DALShowStatement(
                        DALShowStatement.Show.ENGINES,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("ENGINES".equals(text)) {
                return new DALShowStatement(
                        DALShowStatement.Show.ENGINES,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("ERRORS".equals(text)) {
                if (input.isNext(KW_LIMIT, 0)) {
                    info2 = exprParser.expression();
                    if (input.isNext(PUNC_COMMA, 0)) {
                        info1 = info2;
                        input.skipForce(1);
                        info2 = exprParser.expression();
                    }
                }
                return new DALShowStatement(
                        DALShowStatement.Show.ERRORS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("EVENTS".equals(text)) {
                return new DALShowStatement(
                        DALShowStatement.Show.EVENTS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("FUNCTION".equals(text)) {
                if (input.isNext(IDENTIFIER, 0, true, "CODE")) {
                    input.skipForce(1);
                    name1 = match(IDENTIFIER).getText();
                    return new DALShowStatement(
                            DALShowStatement.Show.FUNCTION_CODE,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                } else if (input.isNext(IDENTIFIER, 0, true, "STATUS")) {
                    input.skipForce(1);
                    if (input.isNext(KW_LIKE, 0)) {
                        input.skipForce(1);
                        like = exprParser.expression();
                    } else {
                        match(KW_WHERE);
                        where = exprParser.expression();
                    }
                    return new DALShowStatement(
                            DALShowStatement.Show.FUNCTION_STATUS,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                }
            } else if ("GRANTS".equals(text)) {
                match(KW_FOR);
                name1 = match(IDENTIFIER).getText();
                return new DALShowStatement(
                        DALShowStatement.Show.GRANTS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("OPEN".equals(text)) {
                match(IDENTIFIER, true, "TABLES");
                name1 = match(IDENTIFIER).getText();
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        DALShowStatement.Show.OPEN_TABLES,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("PLUGINS".equals(text)) {
                return new DALShowStatement(
                        DALShowStatement.Show.PLUGINS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("PROFILE".equals(text)) {
                typeList = getTypeList();
                if (input.isNext(KW_FOR, 0) && input.isNext(IDENTIFIER, 0, true, "QUERY")) {
                    input.skipForce(2);
                    info1 = exprParser.expression();
                }
                if (input.isNext(KW_LIMIT, 0)) {
                    input.skipForce(1);
                    info2 = exprParser.expression();
                    if (input.isNext(IDENTIFIER, 0, true, "OFFSET")) {
                        input.skipForce(1);
                        info3 = exprParser.expression();
                    }
                }
                return new DALShowStatement(
                        DALShowStatement.Show.PROFILE,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("PROFILES".equals(text)) {
                return new DALShowStatement(
                        DALShowStatement.Show.PROFILES,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("SLAVE".equals(text)) {
                if (input.isNext(IDENTIFIER, 0, true, "HOSTS")) {
                    return new DALShowStatement(
                            DALShowStatement.Show.SLAVE_HOSTS,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                } else if (input.isNext(IDENTIFIER, 0, true, "STATUS")) {
                    return new DALShowStatement(
                            DALShowStatement.Show.SLAVE_STATUS,
                            property1,
                            name1,
                            name2,
                            info1,
                            info2,
                            info3,
                            info4,
                            typeList,
                            like,
                            where);
                }
            } else if ("GLOBAL".equals(text)) {
                property1 = false;
                if (input.isNext(IDENTIFIER, 0, true, "STATUS")) {
                    show = DALShowStatement.Show.STATUS;
                } else if (input.isNext(IDENTIFIER, 0, true, "VARIABLES")) {
                    show = DALShowStatement.Show.VARIABLES;
                }
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        show,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("SESSION".equals(text)) {
                property1 = true;
                if (input.isNext(IDENTIFIER, 0, true, "STATUS")) {
                    show = DALShowStatement.Show.STATUS;
                } else if (input.isNext(IDENTIFIER, 0, true, "VARIABLES")) {
                    show = DALShowStatement.Show.VARIABLES;
                }
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        show,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("TRIGGERS".equals(text)) {
                if (input.isNext(KW_FROM, 0)) {
                    input.skipForce(1);
                    name1 = match(IDENTIFIER).getText();
                }
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        DALShowStatement.Show.TRIGGERS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if ("WARNINGS".equals(text)) {
                if (input.isNext(KW_LIMIT, 0)) {
                    info2 = exprParser.expression();
                    if (input.isNext(PUNC_COMMA, 0)) {
                        info1 = info2;
                        input.skipForce(1);
                        info2 = exprParser.expression();
                    }
                }
                return new DALShowStatement(
                        DALShowStatement.Show.WARNINGS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            }
            throw err("unexpected token: " + token);
        case KW_TABLE:
            match(IDENTIFIER, true, "STATUS");
            if (input.isNext(KW_FROM, 0)) {
                input.skipForce(1);
                name1 = match(IDENTIFIER).getText();
            }
            if (input.isNext(KW_LIKE, 0)) {
                input.skipForce(1);
                like = exprParser.expression();
            } else {
                match(KW_WHERE);
                where = exprParser.expression();
            }
            return new DALShowStatement(
                    DALShowStatement.Show.TABLE_STATUS,
                    property1,
                    name1,
                    name2,
                    info1,
                    info2,
                    info3,
                    info4,
                    typeList,
                    like,
                    where);
        case KW_PROCEDURE:
            if (input.isNext(IDENTIFIER, 0, true, "CODE")) {
                input.skipForce(1);
                name1 = match(IDENTIFIER).getText();
                return new DALShowStatement(
                        DALShowStatement.Show.PROCEDURE_CODE,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            } else if (input.isNext(IDENTIFIER, 0, true, "STATUS")) {
                input.skipForce(1);
                name1 = match(IDENTIFIER).getText();
                if (input.isNext(KW_LIKE, 0)) {
                    input.skipForce(1);
                    like = exprParser.expression();
                } else {
                    match(KW_WHERE);
                    where = exprParser.expression();
                }
                return new DALShowStatement(
                        DALShowStatement.Show.PROCEDURE_STATUS,
                        property1,
                        name1,
                        name2,
                        info1,
                        info2,
                        info3,
                        info4,
                        typeList,
                        like,
                        where);
            }
            throw err("unexpected token: " + token);
        case KW_INDEX:
            match(KW_FROM);
            name1 = match(IDENTIFIER).getText();
            if (input.isNext(KW_FROM, 0)) {
                input.skipForce(1);
                name2 = match(IDENTIFIER).getText();
            }
            return new DALShowStatement(
                    DALShowStatement.Show.INDEX,
                    property1,
                    name1,
                    name2,
                    info1,
                    info2,
                    info3,
                    info4,
                    typeList,
                    like,
                    where);
        case KW_DATABASES:
            if (input.isNext(KW_LIKE, 0)) {
                input.skipForce(1);
                like = exprParser.expression();
            } else {
                match(KW_WHERE);
                where = exprParser.expression();
            }
            return new DALShowStatement(
                    DALShowStatement.Show.DATABASES,
                    property1,
                    name1,
                    name2,
                    info1,
                    info2,
                    info3,
                    info4,
                    typeList,
                    like,
                    where);
        case KW_CREATE:
            token = input.next();
            if (token == null) throw err("EOF for SHOW");
            switch (token.getType()) {
            case KW_DATABASE:
                name1 = match(IDENTIFIER).getText();
                show = DALShowStatement.Show.CREATE_DATABASE;
                break;
            case KW_PROCEDURE:
                name1 = match(IDENTIFIER).getText();
                show = DALShowStatement.Show.CREATE_PROCEDURE;
                break;
            case KW_TABLE:
                name1 = match(IDENTIFIER).getText();
                show = DALShowStatement.Show.CREATE_TABLE;
                break;
            case KW_TRIGGER:
                name1 = match(IDENTIFIER).getText();
                show = DALShowStatement.Show.CREATE_TRIGGER;
                break;
            case IDENTIFIER:
                if ("EVENT".equalsIgnoreCase(token.getText())) {
                    name1 = match(IDENTIFIER).getText();
                    show = DALShowStatement.Show.CREATE_EVENT;
                    break;
                } else if ("FUNCTION".equalsIgnoreCase(token.getText())) {
                    name1 = match(IDENTIFIER).getText();
                    show = DALShowStatement.Show.CREATE_FUNCTION;
                    break;
                } else if ("VIEW".equalsIgnoreCase(token.getText())) {
                    name1 = match(IDENTIFIER).getText();
                    show = DALShowStatement.Show.CREATE_VIEW;
                    break;
                }
            default:
                throw err("unexpected token: " + token);
            }
            return new DALShowStatement(
                    show,
                    property1,
                    name1,
                    name2,
                    info1,
                    info2,
                    info3,
                    info4,
                    typeList,
                    like,
                    where);
        case KW_CHARACTER:
            match(KW_SET);
            if (input.isNext(KW_LIKE, 0)) {
                input.skipForce(1);
                like = exprParser.expression();
            } else {
                match(KW_WHERE);
                where = exprParser.expression();
            }
            return new DALShowStatement(
                    DALShowStatement.Show.CHARACTER,
                    false,
                    null,
                    null,
                    info1,
                    info2,
                    info3,
                    info4,
                    null,
                    like,
                    where);
        case KW_BINARY:
            match(IDENTIFIER, true, "LOGS");
            return new DALShowStatement(
                    DALShowStatement.Show.LOGS,
                    false,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null);
        default:
            throw err("unexpected token: " + token);
        }
    }

    /** nothing has been pre-consumed */
    public DALSetStatement setStmt() throws SQLSyntaxErrorException {
        match(KW_SET);
        List<Pair<Pair<KeyType, String>, Expression>> list = new LinkedList<Pair<Pair<KeyType, String>, Expression>>();
        while (true) {
            SQLToken token = input.next();
            if (token == null) throw err("unexpected EOF for SET statement");
            Pair<KeyType, String> key;
            String keyText = token.getText();
            KeyType keytype;
            switch (token.getType()) {
            case USER_DEFINED_VARIABLE:
                key = new Pair<KeyType, String>(KeyType.USER, keyText);
                break;
            case SYSTEM_VARIABLE:
                if (keyText.startsWith("@@global.")) {
                    keyText = keyText.substring("@@global.".length());
                    keytype = KeyType.GLOBAL;
                } else if (keyText.startsWith("@@session.")) {
                    keyText = keyText.substring("@@session.".length());
                    keytype = KeyType.SESSION;
                } else {
                    keyText = keyText.substring("@@".length());
                    keytype = KeyType.SESSION;
                }
                key = new Pair<KeyType, String>(keytype, keyText);
                break;
            case IDENTIFIER:
                if ("GLOBAL".equals(keyText.toUpperCase())) {
                    keytype = KeyType.GLOBAL;
                } else if ("SESSION".equals(keyText.toUpperCase())) {
                    keytype = KeyType.SESSION;
                } else {
                    throw err("unexpected for SET statement: " + token);
                }
                keyText = match(IDENTIFIER).getText();
                key = new Pair<KeyType, String>(keytype, keyText);
                break;
            default:
                throw err("unexpected for SET statement: " + token);
            }
            token = input.next();
            switch (token.getType()) {
            case OP_ASSIGN:
            case OP_EQUALS:
                break;
            default:
                throw err("unexpected for SET statement: " + token);
            }
            Expression expr = exprParser.expression();
            list.add(new Pair<Pair<KeyType, String>, Expression>(key, expr));
            if (input.isNext(PUNC_COMMA, 0)) {
                input.skipForce(1);
            } else {
                break;
            }
        }
        return new DALSetStatement(list);
    }

}
